package com.zhou.cloud.service.impl;

import cn.hutool.core.date.DateUtil;
import com.zhou.cloud.service.IDistributedLockService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.core.script.RedisScript;
import org.springframework.stereotype.Service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @descrition: redis实现分布式锁接口
 * @author: zhouyongjun
 * @date: 2022/11/1
 **/
@Service("redisDistributedLockService")
@Slf4j
public class RedisDistributedLockServiceImpl implements IDistributedLockService {
    private static final Long LOCK_SUCCESS = 1L;
    private static final Long RELEASE_SUCCESS = 1L;
    /**
     *  -- 加锁脚本，其中KEYS[]为外部传入参数
     */
   String LUA_LOCK = " local rs = redis.call('setnx', KEYS[1], ARGV[1]) if (rs == 1 or rs == 'ok') then return redis.call('pexpire', KEYS[1], ARGV[2])  else  return 0 end";
    /**
     * 解锁脚本
     */
    String LUA_UNLOCK = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return -1 end";

    @Autowired
    RedisTemplate<String,String> redisTemplate;

    @Override
    public boolean tryLock(String lockKey, String requestId ,TimeUnit timeUnit, int expireTime) {
        Long result = 0L;
        long beginTryTime = System.currentTimeMillis();
        try {
            /*
            //方式一 执行string lua
            final DefaultRedisScript<Long> script = new DefaultRedisScript<>(LUA_LOCK,Long.class);
            */
            //方式二 执行lua脚本文件
            final DefaultRedisScript<Long> script = new DefaultRedisScript();
            script.setLocation(new ClassPathResource("lua/redis_lock.lua"));
            script.setResultType(Long.class);
            List<String> list =new ArrayList<>();
            list.add(lockKey);
            result= redisTemplate.execute(script,list,requestId,expireTime);
        } catch (Exception e) {
            log.error("", e);
        }
        log.info("tryLock lockKey={},requestId={} expireTime={},result={},cost(ms)={}",lockKey,requestId,expireTime,LOCK_SUCCESS.equals(result),System.currentTimeMillis()-beginTryTime);
        return LOCK_SUCCESS.equals(result);
    }

    @Override
    public boolean tryLock(String lockKey, String requestId, TimeUnit timeUnit, int expireTime, int waitingTime) {

        boolean isLock = false;
        long beginTryTime = System.currentTimeMillis();
        log.info("tryLock wait begin lockKey={},requestId={} beginTime={},outTime={}",lockKey,requestId,
                DateUtil.date(beginTryTime).toString("yyyy-MM-dd HH:mm:ss.SSS"),
                DateUtil.date(beginTryTime+waitingTime).toString("yyyy-MM-dd HH:mm:ss.SSS"));
        try {
            //waitingTime ms 循环获取锁标记，超过则直接不用等待,锁超时10ms过去

            long lastTryTime = beginTryTime;
            long endTime = beginTryTime + waitingTime >0 ? timeUnit.toMillis(waitingTime) : 0;
            while (!(isLock=tryLock(lockKey, requestId,timeUnit,expireTime)) && lastTryTime < endTime) {
                try {
                    Thread.sleep(10L);
                } catch (Exception e) {
                    log.error("",e);
                }
                lastTryTime = System.currentTimeMillis();
            }
        } catch(Exception e) {
            log.error("",e);
        }
        log.info("tryLock wait end lockKey={},requestId={} expireTime={},waitingTime={},outTime={},result={},cost(ms)={}",lockKey,requestId,expireTime,waitingTime,
                DateUtil.date(beginTryTime+waitingTime).toString("yyyy-MM-dd HH:mm:ss.SSS"),isLock,System.currentTimeMillis()-beginTryTime);
        return isLock;
    }


    /**
     * 释放分布式锁
     * @param lockKey 锁
     * @param requestId 请求标识
     * @return 是否释放成功
     */
    @Override
    public boolean releaseLock(String lockKey, String requestId) {
        Long result = 0L;
        try {
           /**
            //方式一 通过string 执行lua
           final DefaultRedisScript<Long> script = new DefaultRedisScript<>(LUA_UNLOCK,Long.class);
           */
            //方式二 执行lua脚本文件
            final DefaultRedisScript<Long> script = new DefaultRedisScript();
            script.setLocation(new ClassPathResource("lua/redis_release_lock.lua"));
            script.setResultType(Long.class);
            List<String> list =new ArrayList<>();
            list.add(lockKey);
            result= redisTemplate.execute(script,list,requestId);


        } catch (Exception e) {
            log.error("", e);
        }
        log.info("releaseLock lockKey={},requestId={},result={}",lockKey,requestId,RELEASE_SUCCESS.equals(result));
        return RELEASE_SUCCESS.equals(result);

    }
}
